#pragma once

#include "afwstring.hpp"
#include <stdlib.h>

namespace one_rag {
  namespace impl {

    template<typename DataT>
    class Buffer {
      const DataT* _data;
      RefBuffer<DataT>* _alloc;

    public:
      Buffer(const DataT* init=0);
      Buffer(const Buffer& rhs);
      ~Buffer();

      operator DataT*();
      Buffer& operator=(const Buffer& rhs);
      //DataT* operator&();
      DataT* operator->();
      DataT& operator*();
      DataT& operator[](unsigned i);
      const DataT* operator->() const;
      const DataT& operator*() const;

      void AllocCb(long len);
      void Alloc(long elt=-1);
      long Size() const;
    };



    template<typename DataT>
    inline Buffer<DataT>::Buffer(const DataT* init):_data(init),_alloc(0){
    }

    template<typename DataT>
    inline Buffer<DataT>::Buffer(const Buffer& rhs){
      _data = rhs._data;
      if((_alloc = rhs._alloc))
        _alloc->refCount++;
    }

    template<typename DataT>
    inline Buffer<DataT>::~Buffer(){
      _data = 0;
      if(_alloc){
        if(!--_alloc->refCount)
          free(_alloc);
      }
      _alloc=0;
    }

    template<typename DataT>
    inline Buffer<DataT>& Buffer<DataT>::operator=(const Buffer<DataT>& rhs){
      if(this != &rhs){
        _data = rhs._data;
        RefBuffer<char>* old= _alloc;
        if((_alloc = rhs._alloc))
          _alloc->refCount++;
        if(old){
          if(!--old->refCount)
            free(old);
        }
      }
      return *this;
    }

    template<typename DataT>
    inline Buffer<DataT>::operator DataT*(){
      Alloc();
      return _alloc->data;
    }

    template<typename DataT>
    inline DataT* Buffer<DataT>::operator->(){
      Alloc();
      return const_cast<DataT*>(_data);
    }

    template<typename DataT>
    inline void Buffer<DataT>::Alloc(long len){
      if(_alloc && _alloc->refCount >1){
        _alloc->refCount--;
        _alloc=0;
        _data=0;
      }
      if(!len){
        if(_alloc){
          free(_alloc);
          _alloc=0;
          _data=0;
        }
     }
     else if(len != -1){
       RefBuffer<DataT>* newBuf = (RefBuffer<DataT>*)realloc(_alloc,sizeof(RefBuffer<DataT>)+sizeof(DataT)*len);
       if(newBuf){
         _alloc = newBuf;
         _alloc->refCount = 1;
         _alloc->bufSize = len;
         _data = _alloc->data;
       }
     }
    }

    template<typename DataT>
    inline long Buffer<DataT>::Size() const {
      if(_alloc)
        return _alloc->bufSize;
      return 0;
    }

    template<>
    class Buffer<void> {
      const void* _data;
      RefBuffer<char>* _alloc;
    public:
      Buffer():_data(0),_alloc(0){
      }
      Buffer(const Buffer& rhs);
      ~Buffer(){
        if(_alloc && !--_alloc->refCount)
          delete _alloc;
        _alloc = 0;
        _data = 0;
      }

      void Alloc(long len) {
        _data = 0;
        if(_alloc && _alloc->refCount >1){
          _alloc->refCount--;
          _alloc=0;
        }
        else {
          if(!len){
            if(_alloc){
              free(_alloc);
              _alloc=0;
            }
          }
          else if(len != -1){
            if(RefBuffer<char>* newBuf = (RefBuffer<char>*)realloc(_alloc,sizeof(RefBuffer<char>)+sizeof(char)*len)){
              _alloc = newBuf;
              _alloc->refCount = 1;
            }
          }
        }
      }
    };

  } // impl

  typedef impl::Buffer<void> Data;

} // one_rag
